/**************************************************************************
 * Copyright (C) 2017-2017  Unisound
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 **************************************************************************
 *
 * Description : hb_noise_reduction.c
 * Author      : shangjinlong@unisound.com
 * Date        : 2020.07.01
 *
 **************************************************************************/
#include "unione.h"
#include "uni_uart.h"
#include "uni_channel.h"
#include "user_event.h"
#include "uni_config.h"
#include "uni_hal_mailbox.h"
#include "uni_databuf.h"
#include "uni_hal_ssp_play.h"

#define TAG                     "noise_reduction"
#define KWS_AUDIO_DATABUF_SIZE  (32 * 1024)

static DataBufHandle g_kws_audio_buf  = NULL;
static uni_u32       g_audio_skip_cnt = 0;
static uni_bool      g_need_push_data = false;
static UartHandle    g_uart_handle    = NULL;

static int _uart_write(char *buf, int len) {
  return UartWrite(buf, len, g_uart_handle);
}

void hb_start_push_kws_audio_data() {
  g_need_push_data = true;
}

void hb_stop_push_kws_audio_data() {
  g_need_push_data = false;
}

static void _subscribe_event_cb(USER_EVENT_TYPE event, user_event_context_t *context) {
  LOGT(TAG, "recv subscribe event. [%d]", event);
  static ChnlAwakenReq awaken_req = {0};
  switch (event) {
    case USER_AUDIO_PLAY_START:
      break;
    case USER_AUDIO_PLAY_END:
      break;
    case USER_CUSTOM_SETTING:
      awaken_req.session_id++;
      awaken_req.type = CHNL_MESSAGE_AWAKEN_REQUEST;
      snprintf(awaken_req.content, sizeof(awaken_req.content), "%s", context->custom_setting.word_str);
      ChnlAwakenRequest(&awaken_req);
      break;
    case USER_VOLUME_SETTING:
      awaken_req.session_id++;
      awaken_req.type = CHNL_MESSAGE_AWAKEN_REQUEST;
      snprintf(awaken_req.content, sizeof(awaken_req.content), "%s", context->voluem_setting.word_str);
      ChnlAwakenRequest(&awaken_req);
      break;
    case USER_GOTO_SLEEPING:
      awaken_req.session_id++;
      awaken_req.type = CHNL_MESSAGE_AWAKEN_REQUEST;
      if (EVENT_TRIGGER_AUTO == context->goto_sleeping.trigger) {
        snprintf(awaken_req.content, sizeof(awaken_req.content), "%s", "设备超时退出");
      } else {
        snprintf(awaken_req.content, sizeof(awaken_req.content), "%s", context->goto_sleeping.word_str);
      }
      ChnlAwakenRequest(&awaken_req);
      break;
    case USER_GOTO_AWAKENED:
      awaken_req.session_id++;
      awaken_req.type = CHNL_MESSAGE_AWAKEN_REQUEST;
      snprintf(awaken_req.content, sizeof(awaken_req.content), "%s", context->goto_awakend.word_str);
      ChnlAwakenRequest(&awaken_req);
      break;
    default:
      LOGW(TAG, "undefine event[%d] fatal error.", event);
      break;
  }
}

static void _register_subscribe_event(void) {
  for (int i = USER_INVALID_EVENT + 1; i < USER_EVENT_MAX; i++) {
    user_event_subscribe_event(i, _subscribe_event_cb);
  }
}

static int _enable_hifi_record(void) {
  config_context_t config;
  config.type                  = CONFIG_RECORD;
  config.items.record.enable   = 1;
  config.items.record.asr_only = 1;
  return ConfigSendToHIFI(&config);
}

static int _send_recv_ack(void) {
  int ret;
  mailbox_message msg;
  msg.taskId = MAILBOX_RECORD;
  msg.size   = 0;
  if(0 != (ret = sys_hal_mailbox_send(&msg))) {
    LOGE(TAG, "sys_hal_mailbox_send failed !");
  }
  return ret;
}

static int _recv_record_data(char *args, mailbox_message *message) {
  invalidate_dcache_range(message->data[0], message->data[0] + message->data[1]);

  if (DataBufferGetFreeSize(g_kws_audio_buf) < message->data[1]) {
    LOGD(TAG, "raw data buffer not enough");
    g_audio_skip_cnt++;
  } else {
    DataBufferWrite(g_kws_audio_buf, (char *)message->data[0], message->data[1]);
  }

  return _send_recv_ack();
}

static void _kws_audio_databuf_create() {
  g_kws_audio_buf = DataBufferCreate(KWS_AUDIO_DATABUF_SIZE);
  assert(g_kws_audio_buf != NULL);
}

static void _kws_audio_record_register() {
  //step1. 配置hifi record类型
  _enable_hifi_record();

  //step2. 创建audio存储buffer
  _kws_audio_databuf_create();

  //step3. 注册kws audio callback
  sys_hal_mailbox_register_callback(MAILBOX_RECORD, _recv_record_data, NULL);
}

static void _push_data_2_iot_device(void *args) {
  int frame_bytes;
#if NOISE_REDUCTION_MODE == UART_TRANSMIT_MODE //uart模式
  static ChnlNoiseReductionPcmData pcm;//静态变量，不占用线程栈空间
  pcm.type = CHNL_MESSAGE_NOISE_REDUCTION_RAW_DATA;
  frame_bytes = sizeof(pcm.data);
#elif NOISE_REDUCTION_MODE == I2S_TRANSMIT_MODE //I2S模式
  static char i2s_feed_buffer[2048];
  frame_bytes = sizeof(i2s_feed_buffer);
#else
  #error "only support UART or I2S transmit, fatal error"
#endif
  int push_failed_cnt = 0;
  int push_success_cnt = 0;
  uni_u32 start = 0, now;
  while (true) {
    //step1. 检测数据量是否足够一帧，如果不够则等待16ms，不用信号量处理
    if (DataBufferGetDataSize(g_kws_audio_buf) < frame_bytes) {
      uni_msleep(16);
      continue;
    }

    //step2. 读取一帧数据
#if NOISE_REDUCTION_MODE == UART_TRANSMIT_MODE
    DataBufferRead(pcm.data, frame_bytes, g_kws_audio_buf);
#elif NOISE_REDUCTION_MODE == I2S_TRANSMIT_MODE
    DataBufferRead(i2s_feed_buffer, frame_bytes, g_kws_audio_buf);
#else
    #error "only support UART or I2S transmit, fatal error"
#endif

    //step3. 推送数据到IoT设备
    if (g_need_push_data) {
#if NOISE_REDUCTION_MODE == UART_TRANSMIT_MODE
      if (0 != ChnlNoiseReductionPcmDataPush(&pcm)) {
        LOGW(TAG, "push data failed. [%d]", ++push_failed_cnt);
      } else {
        push_success_cnt++;
      }
#elif NOISE_REDUCTION_MODE == I2S_TRANSMIT_MODE
      uni_hal_ssp_dac_play((unsigned char*)i2s_feed_buffer, sizeof(i2s_feed_buffer));
#else
  #error "only support UART or I2S transmit, fatal error"
#endif
      // debug信息，统计丢包率，buffer利用率
      if (start == 0) start = uni_get_clock_time_ms();
      now = uni_get_clock_time_ms();
      if (now - start >= 2000) { //采样频率2s
        static uni_u32 data_cnt_max = 0;
        static uni_u32 data_cnt_min = (uni_u32)-1;
        static uni_u32 sampling_times = 0;
        static uni_u32 total_data_cnt = 0;
        uni_u32 cur_data_cnt = DataBufferGetDataSize(g_kws_audio_buf) >> 10;
        uni_u32 data_cnt_avg;
        total_data_cnt = total_data_cnt + cur_data_cnt;
        data_cnt_max   = uni_max(data_cnt_max, cur_data_cnt);
        data_cnt_min   = uni_min(data_cnt_min, cur_data_cnt);
        sampling_times++;
        data_cnt_avg = total_data_cnt / sampling_times;
        LOGT(TAG, "drop info[%d,%d,%d]. buffer info[%d,%d,%d,%d].",
             push_success_cnt, push_failed_cnt, g_audio_skip_cnt,
             sampling_times, data_cnt_max, data_cnt_min, data_cnt_avg);
        start = now;
      }
    }
  }
}

static void _start_kws_audio_record_data_push_task() {
  uni_pthread_t pid;
  struct thread_param param;
  param.stack_size  = STACK_SMALL_SIZE;
  param.th_priority = OS_PRIORITY_HIGH;
  snprintf(param.task_name, sizeof(param.task_name), "%s", "audio-push");
  uni_pthread_create(&pid, &param, _push_data_2_iot_device, NULL);
}

int hb_noise_reduction(void) {
  //step1. 初始化uart
  UartConfig uartConfig = {UNI_UART3, UNI_B_921600, UNI_PARITY_NONE, UNI_ONE_STOP_BIT, 8};
  g_uart_handle = UartInitialize(&uartConfig, CommProtocolReceiveUartData);
  assert(NULL != g_uart_handle);

  //step2. 初始化ucp协议栈
  CommProtocolInit(_uart_write, ChnlReceiveCommProtocolPacket);

  //step3. 订阅user事件
  _register_subscribe_event();

  //step4. 启动ucp推送降噪数据任务
  _start_kws_audio_record_data_push_task();

  //step5. 初始化KWS audio record
  _kws_audio_record_register();

  return 0;
}
